
昨天叢集起來了,今天把一隻真的 bot 搬進去。
我拿 OpenAB 艦隊裡最單純的一隻當例子:它是一個長駐的 bot 容器,需要幾個 token(機密)、幾個設定值(非機密),然後一直跑著。這正好對應 K8s 的三個最小物件:Secret、ConfigMap、Deployment。
一樣先聲明:這篇的 YAML 是我從 compose 定義翻譯過來的最小版本,image 名稱用 placeholder,寫這篇時還沒在叢集上實跑驗證(verified: false)。
在 compose 裡,這隻 bot 長這樣(簡化):一個 image、一個 env_file 放 token、幾個 environment 放設定、restart: unless-stopped。翻譯對照:
env_file 裡的 token → Secret
environment 裡的一般設定 → ConfigMap
image + restart → Deployment(replicas: 1)Day 11 說過「compose 裡寫下的答案就是搬家時的輸入」,就是這個意思。
Secret 我不寫成 YAML 檔。compose 用的 .env 已經有 token 了,直接 kubectl create secret generic bot-secrets --from-env-file=.env 從它產生,是從 compose 平移最省事的做法,也不會有一份明文 token 躺在 repo 裡等著被 commit。
注意:Secret 在 K8s 裡預設不是加密的,K8s 會幫你 base64 編碼存起來,但編碼不是加密,這點跟很多人的直覺不同。真正的機密管理(外部 vault、加密 etcd)超出本系列範圍,官方說明見 Secrets 與 Encrypting Confidential Data at Rest。
ConfigMap 則是非機密設定,寫成檔案 commit 進 repo 沒問題(config.yaml):
apiVersion: v1
kind: ConfigMap
metadata:
name: bot-config
data:
LOG_LEVEL: "info"
BOT_NAME: "my-first-bot"
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-first-bot
spec:
replicas: 1
selector:
matchLabels: { app: my-first-bot }
template:
metadata:
labels: { app: my-first-bot }
spec:
containers:
- name: bot
image: ghcr.io/example/my-bot:latest
envFrom:
- secretRef: { name: bot-secrets }
- configMapRef: { name: bot-config }
resources:
requests: { memory: "64Mi", cpu: "50m" }
limits: { memory: "256Mi" }
幾個我當初卡住的點:
selector.matchLabels 和 template.metadata.labels 必須一致,不然 Deployment 找不到自己的 Pod。envFrom 一次把整個 Secret / ConfigMap 灌成環境變數,跟 compose 的 env_file 行為最接近。resources 我刻意寫了。我實測的 bot 容器記憶體多在個位數到數十 MiB,所以 request 給 64Mi 很寬裕;limit 給 256Mi 是留給突發。Day 09 講過 limit 太小會被 OOMKilled,這裡先給寬一點,觀察後再收。先把 image 換成你自己的;照抄 example 的 placeholder 會 ImagePullBackOff。然後依序:
kubectl create secret generic bot-secrets --from-env-file=.env(Secret 只從這裡來,不 apply 任何 YAML)kubectl apply -f config.yaml -f deployment.yaml
kubectl get pods -l app=my-first-bot
kubectl logs -l app=my-first-bot --tail=50
看到 Running 不代表成功:bot 可能起來了但 token 錯、連不上平台。所以第四步一定要跑,看它自己的 log 說什麼。如果看到的是 CrashLoopBackOff,明天見。
最大的差別不是 YAML 比較長,而是:compose 的 restart: unless-stopped 是「這台機器上的這個容器掛了就重啟」;Deployment 的 replicas: 1 是「要一直維持一個這樣的 Pod,掛了就補、補不起來就一直試」。
前者綁定機器,後者綁定期望狀態。這個差別在單機 k3s 上感覺不到,到雲端多節點才會體會到。
compose 檔裡的每一行,都已經是 K8s 物件的答案;搬過去的工作是翻譯,不是重寫。